import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;

import model.Employee;
import model.Fly;
import model.FlyEmployee;
import model.comparator.EmployeeGenderComparator;
import model.comparator.EmployeeWillFlyComparator;
import model.comparator.MultiComparator;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

public class Calculate
{
	public static final int	GENERATE_MODE_INITIALIZE				= 0;
	public static final int	GENERATE_MODE_FILL						= 1;

	public final static int	CHECK_AVAILABLE							= 0;
	public final static int	CHECK_AVAILABLE_FULL					= 1;
	public final static int	CHECK_AVAILABLE_EXIST					= 2;
	public final static int	CHECK_AVAILABLE_EMPLOYEE_NOT_RELAXED	= 3;

	public final static int	EMPLOYEE_RELAX_HOUR_8					= 8;
	public final static int	EMPLOYEE_RELAX_HOUR_36					= 36;
	public final static int	EMPLOYEE_RELAX_HOUR_ON_DST_60			= 60;

	public final static int	QUANTITY_OF_STEWARDS_A					= 4 - 1;
	public final static int	QUANTITY_OF_STEWARDS_B					= 6 - 2;
	public final static int	QUANTITY_OF_SENIOR_ASSIST_STEWARDS		= 2;
	public final static int	QUANTITY_OF_SENIOR_STEWARDS				= 2;
	public final static int	QUANTITY_OF_ALL_A						= 6;
	public final static int	QUANTITY_OF_ALL_B						= 8;

	public final static int	DUTY_TIME_PREFIX_MINUTE					= 90;
	public final static int	DUTY_TIME_POSTFIX_MINUTE				= 30;

	public Calculate()
	{

	}

	public static void setEmployeeToFly(Session session, Fly fly, Employee employee, boolean specified)
	{
		FlyEmployee flyEmployee = new FlyEmployee();
		flyEmployee.setEmployee(employee);
		flyEmployee.setFly(fly);
		flyEmployee.setSpecified(specified);
		flyEmployee.setStart(fly.getDate1());
		if ( fly.isVia() )
		{
			employee.setArriveDate(fly.getDate8());
			flyEmployee.setEnd(fly.getDate8());
			employee.setBerlin(employee.getBerlin() + Employee.getDateDifference(fly.getDate4(), fly.getDate5()));
		}
		else
		{
			employee.setArriveDate(fly.getDate4());
			flyEmployee.setEnd(fly.getDate4());
		}
		session.save(flyEmployee);
		session.save(employee);
	}

	public static void setEmployeesToFly(Session session, Fly fly, ArrayList<Employee> employees)
	{
		Transaction transaction = session.beginTransaction();
		for (Employee employee : employees)
		{
			setEmployeeToFly(session, fly, employee, false);
		}
		transaction.commit();
	}

	public static void removeEmployeeFromFly(Session session, Fly fly, Employee employee)
	{
		Transaction transaction = session.beginTransaction();

		FlyEmployee removeFlyEmployee = FlyEmployee.getFlyEmployeeByFlyAndEmployee(session, fly, employee);
		session.delete(removeFlyEmployee);
		session.save(employee);
		transaction.commit();
	}

	public static float flyHour(Fly fly)
	{
		int flyId = (int) fly.getDstPosition().getId();
		switch (flyId)
		{
			case 1:
				return 0;
			case 2:
				return 4.5f;
			case 3:
				return 8.83f;
			case 4:
				return 6.5f;
			case 5:
				return 10.25f;
			case 6:
				return 10.25f;
			case 7:
				return 8.25f;
			case 8:
				return 17.5f;
			default:
				return 0;
		}
	}

	public static long averageFlyTimeByHour(Session session, int category)
	{
		long allminute = 0;
		float currentFlyMinute = 0;
		ArrayList<Fly> flies = Fly.getFliesListByThisMonth(session);
		for (Fly fly : flies)
		{
			currentFlyMinute = flyHour(fly);
			if ( fly.getAirPlane().getId() == 1 || fly.getAirPlane().getId() == 2 )
				currentFlyMinute *= 10;
			else
				currentFlyMinute *= 8;
			allminute += currentFlyMinute;
		}
		long employeeCount = (Long) session.createCriteria(Employee.class).setProjection(Projections.rowCount()).uniqueResult();
		return allminute / employeeCount;
		// return 95;
	}

	@SuppressWarnings("unchecked")
	public static ArrayList<Employee> getAvailableEmployeeWithAverageFlyTime(Session session, int category, boolean withAverageHour, long averageHour, boolean orderMaleGender, Fly willFly)
	{
		long averageFlyHour;
		if ( withAverageHour )
		{
			averageFlyHour = averageHour;
		}
		else
		{
			averageFlyHour = averageFlyTimeByHour(session, category);
		}
		ArrayList<Employee> availableEmployees = new ArrayList<Employee>();
		ArrayList<Employee> allEmployees = (ArrayList<Employee>) session.createCriteria(Employee.class).list();
		for (Employee employee : allEmployees)
		{
			Log.d(employee.getCategoryName() + ">>>>>>>>>>>>>>>>>> " + employee.getWillFlyTimeOnThisMonth(true, session) + " : " + averageFlyHour);
			if ( willFly == null )
			{
				if ( employee.getCategory() == category && (long) employee.getWillFlyTimeOnThisMonth(true, session) < averageFlyHour )
				{
					availableEmployees.add(employee);
				}
			}
			else
			{
				if ( employee.getCategory() == category && (long) employee.getWillFlyTimeOnThisMonth(true, session) + flyHour(willFly) < averageFlyHour )
				{
					availableEmployees.add(employee);
				}
			}
		}
		MultiComparator multiComparator = new MultiComparator();
		if ( orderMaleGender )
		{
			multiComparator.addComparator(new EmployeeGenderComparator());
		}
		multiComparator.addComparator(new EmployeeWillFlyComparator());
		Collections.sort(availableEmployees, multiComparator);
		return availableEmployees;
	}

	public static long gen(Session session, int mode, int category, boolean withAverageHour, long averageHour)
	{
		boolean breakFlyFor = false;
		ArrayList<Fly> flies = Fly.getFliesListByThisMonth(session);
		ArrayList<Employee> availableEmployees = getAvailableEmployeeWithAverageFlyTime(session, category, withAverageHour, averageHour, false, null);
		while (availableEmployees.size() > 0 && flies.size() > 0)
		{
			for (Fly fly : flies)
			{
				long maleEmployeesCount = (Long) session.createCriteria(Fly.class).add(Restrictions.idEq(fly.getId())).createCriteria("flyEmployees").createCriteria("employee").add(Restrictions.eq("gender", Employee.EMPLOYEE_GENDER_MALE)).setProjection(Projections.rowCount()).uniqueResult();
				if ( maleEmployeesCount == 0 )
				{
					availableEmployees = getAvailableEmployeeWithAverageFlyTime(session, category, withAverageHour, averageHour, true, fly);
				}
				else
				{
					availableEmployees = getAvailableEmployeeWithAverageFlyTime(session, category, withAverageHour, averageHour, false, fly);
				}
				for (Employee employee : availableEmployees)
				{
					int availableResult = checkAvailability(session, fly, employee, category);

					Calculate calculate = new Calculate();
					calculate.logAvailability(availableResult);

					if ( availableResult == CHECK_AVAILABLE )
					{
						Transaction transaction = session.beginTransaction();

						FlyEmployee flyEmployee = new FlyEmployee();
						flyEmployee.setFly(fly);
						flyEmployee.setEmployee(employee);
						session.save(flyEmployee);

						fly.getFlyEmployees().add(flyEmployee);
						session.merge(fly);
						employee.getFlyEmployees().add(flyEmployee);
						session.merge(employee);
						breakFlyFor = true;

						transaction.commit();
						break;
					}
					else if ( availableResult == CHECK_AVAILABLE_FULL )
					{
						breakFlyFor = true;
					}
					else if ( availableResult == CHECK_AVAILABLE_EXIST )
					{
						continue;
					}
				}
				if ( breakFlyFor )
				{
					break;
				}
			}
			flies = filterFullFly(mode, session, flies, category);
			Log.d("**************************************************************");
			Log.d("* availableEmployees size " + availableEmployees.size());
			Log.d("**************************************************************");
		}
		return averageFlyTimeByHour(session, category);
	}

	@SuppressWarnings("unchecked")
	public static int checkAvailability(Session session, Fly fly, Employee employee, int category)
	{
		// Тухайн нислэгт тухайн үйлчилэгч нь хуваарилагдсан эсэх.
		Criteria flyEmployeeCriteria = session.createCriteria(FlyEmployee.class);
		flyEmployeeCriteria.add(Restrictions.eq("fly", fly));
		ArrayList<FlyEmployee> flyEmployees = (ArrayList<FlyEmployee>) flyEmployeeCriteria.list();
		for (FlyEmployee flyEmployee : flyEmployees)
		{
			if ( flyEmployee.getEmployee().getId() == employee.getId() )
			{
				return CHECK_AVAILABLE_EXIST;
			}
		}
		// Тухайн нислэгт үйлчилэгчийн бусад/өмнөх/ нислэгүүд зохицож байгаа
		// эсэх.
		Criteria flyCriteria = session.createCriteria(Fly.class);
		flyCriteria.addOrder(Order.desc("date1"));
		flyCriteria.setMaxResults(1);
		Criteria flyEmployeeCriteria2 = flyCriteria.createCriteria("flyEmployees");
		flyEmployeeCriteria2.add(Restrictions.eq("employee", employee));

		Fly beforeFly = (Fly) flyCriteria.uniqueResult();
		if ( beforeFly != null )
		{
			boolean relax60 = false;
			if ( beforeFly.isVia() )
			{
				if ( StaticMethods.getDateDifferenceByHour(beforeFly.getDate5(), beforeFly.getDate4()) >= EMPLOYEE_RELAX_HOUR_ON_DST_60 )
				{
					relax60 = true;
				}
			}
			else
			{
				if ( StaticMethods.getDateDifferenceByHour(beforeFly.getDate3(), beforeFly.getDate2()) >= EMPLOYEE_RELAX_HOUR_ON_DST_60 )
				{
					relax60 = true;
				}
			}

			Date arriveDate = incrementDateByMinute(fly.getArriveDate(), DUTY_TIME_POSTFIX_MINUTE);
			Date leaveDate = decrementDateByMinute(beforeFly.getLeaveDate(), DUTY_TIME_PREFIX_MINUTE);
			if ( relax60 )
			{
				if ( StaticMethods.getDateDifferenceByHour(arriveDate, leaveDate) < EMPLOYEE_RELAX_HOUR_36 )
				{
					return CHECK_AVAILABLE_EMPLOYEE_NOT_RELAXED;
				}
			}
			else
			{
				if ( StaticMethods.getDateDifferenceByHour(arriveDate, leaveDate) < EMPLOYEE_RELAX_HOUR_8 )
				{
					return CHECK_AVAILABLE_EMPLOYEE_NOT_RELAXED;
				}
			}
		}
		// Тухайн үйлчилэгч боломжтой.
		return CHECK_AVAILABLE;
	}

	public static Date incrementDateByMinute(Date date, int minute)
	{
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.add(Calendar.MINUTE, minute);
		return calendar.getTime();
	}

	public static Date decrementDateByMinute(Date date, int minute)
	{
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);
		calendar.add(Calendar.MINUTE, minute);
		return calendar.getTime();
	}

	public static ArrayList<Fly> filterFullFly(int mode, Session session, ArrayList<Fly> flies, int category)
	{
		ArrayList<Fly> filteredFlies = new ArrayList<Fly>();
		for (Fly fly : flies)
		{
			long count = 0;
			if	(mode == GENERATE_MODE_FILL){
				count = (Long) session.createCriteria(FlyEmployee.class).add(Restrictions.eq("fly", fly)).createCriteria("employee").setProjection(Projections.rowCount()).uniqueResult();
				if ( fly.getAirPlane().getId() == 1 || fly.getAirPlane().getId() == 2 )
				{
					if ( QUANTITY_OF_ALL_B <= count )
					{
						continue;
					}
				}
				else
				{
					if ( QUANTITY_OF_ALL_A <= count )
					{
						continue;
					}
				}
			}else if (mode == GENERATE_MODE_INITIALIZE){
				count = (Long) session.createCriteria(FlyEmployee.class).add(Restrictions.eq("fly", fly)).createCriteria("employee").add(Restrictions.eq("category", category)).setProjection(Projections.rowCount()).uniqueResult();
	
				switch (category)
				{
					case Employee.EMPLOYEE_CATEGORY_SENIOR_ASSIST_STEWARDS:
						if ( QUANTITY_OF_SENIOR_ASSIST_STEWARDS <= count )
						{
							continue;
						}
						break;
					case Employee.EMPLOYEE_CATEGORY_SENIOR_STEWARDS:
						if ( QUANTITY_OF_SENIOR_STEWARDS <= count )
						{
							continue;
						}
						break;
					case Employee.EMPLOYEE_CATEGORY_STEWARDS:
						if ( fly.getAirPlane().getId() == 1 || fly.getAirPlane().getId() == 2 )
						{
							if ( QUANTITY_OF_STEWARDS_B <= count )
							{
								continue;
							}
						}
						else
						{
							if ( QUANTITY_OF_STEWARDS_A <= count )
							{
								continue;
							}
						}
						break;
					default:
						break;
				}
			}
			filteredFlies.add(fly);
		}
		return filteredFlies;
	}

	public void logAvailability(int result)
	{
		switch (result)
		{
			case CHECK_AVAILABLE:
				Log.d("available");
				break;
			case CHECK_AVAILABLE_FULL:
				Log.d("not available: full");
				break;
			case CHECK_AVAILABLE_EXIST:
				Log.d("not available: exists");
				break;
			case CHECK_AVAILABLE_EMPLOYEE_NOT_RELAXED:
				Log.d("not available: not relaxed");
				break;
			default:
				Log.d("not available: undefined");
				break;
		}
	}
}